/*
 * MIT License
 *
 * Copyright (c) 2019 Mr.css
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package cn.seaboot.commons.core;


import cn.seaboot.commons.exception.ServiceException;

/**
 * Twitter 的 Snowflake 算法
 * <p>
 * Snowflake ID 总长度 64 位：正数位(1 bit)+ 时间戳(41 bit)+ 机房ID(5 bit)+ 机器ID(5 bit)+ 自增序列(12 bit)
 *
 * <pre>
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
 * </pre>
 * <p>
 * mysql 字段类型： bigint，java 类型： long
 * <p>
 * long 类型第一位是符号位，为了保证整数，固定填 0
 * <p>
 * 致命缺陷：极度依赖服务器时间，服务器时钟回拨时可能会生成重复 id；
 * 推荐一张表一个算法实例，因为不同表的主键允许重复，可以减少线程之间的竞争；
 *
 * @author Mr.css
 * @version 2020-11-26 16:00 私有化函数
 */
public class Snowflake {

    /**
     * 机房ID
     */
    private final long datacenterId;

    /**
     * 机器ID
     */
    private final long workerId;

    /**
     * 自增序列起始值
     */
    private long sequence = 0L;

    /**
     * 起始时间戳，默认 41 位可以用 69 年
     * <p>
     * 需要避免代码误修改，如果想延长使用寿命，可以减少机器码或者序列的长度。
     * <p>
     * 推荐：优先减少序列的长度，因为加了 synchronized 关键字的函数，大部分设备不可能在  1ms 内并发执行 4096 次。
     * <p>
     * 注意：这些规则，项目初期就要定好。
     */
    private long epoch = 1732075395421L;

    /**
     * 机房 ID 位数，默认 5 位
     */
    private long datacenterIdBits = 5L;

    /**
     * 机器 ID 位数，默认 5 位
     */
    private long workerIdBits = 5L;

    /**
     * 自增序列位数，默认 12 位
     */
    private long sequenceBits = 12L;

    /**
     * 时间戳需要向左移 22 位
     */
    private final long timestampLeftShift;

    /**
     * 自增序列的掩码 4095 (0xfff)
     */
    private final long sequenceMask;

    /**
     * 10 位设备码
     */
    private long machineCode = 0L;

    /**
     * 上次生成ID的时间截，记录产生时间毫秒数，判断是否是同1毫秒
     */
    private long lastTimestamp = -1L;


    /**
     * 构造函数
     *
     * @param datacenterId 机房 ID
     * @param workerId     机器 ID
     */
    public Snowflake(long datacenterId, long workerId) {
        this(1732075395421L, datacenterId, workerId, 5, 5, 12);
    }

    /**
     * 构造函数
     *
     * @param epoch        其实时间
     * @param datacenterId 机房 ID
     * @param workerId     机器 ID
     */
    public Snowflake(long epoch, long datacenterId, long workerId) {
        this(epoch, datacenterId, workerId, 5, 5, 12);
    }

    /**
     * 构造函数
     *
     * @param datacenterId     机房 ID
     * @param workerId         机器 ID
     * @param datacenterIdBits 机房位数，默认 5 位
     * @param workerIdBits     机器位数，默认 5 位
     * @param sequenceBits     序列位数，默认 12 位
     */
    public Snowflake(long datacenterId, long workerId, long datacenterIdBits, long workerIdBits, long sequenceBits) {
        this(1732075395421L, datacenterId, workerId, datacenterIdBits, workerIdBits, sequenceBits);
    }

    /**
     * 构造函数
     *
     * @param epoch            其实时间
     * @param datacenterId     机房 ID
     * @param workerId         机器 ID
     * @param datacenterIdBits 机房位数，默认 5 位
     * @param workerIdBits     机器位数，默认 5 位
     * @param sequenceBits     序列位数，默认 12 位
     */
    public Snowflake(long epoch, long datacenterId, long workerId, long datacenterIdBits, long workerIdBits, long sequenceBits) {
        this.datacenterIdBits = datacenterIdBits;
        this.workerIdBits = workerIdBits;
        this.sequenceBits = sequenceBits;

        // 默认：只能 5 bit，也就是 0-31
        long maxDatacenterId = ~(-1L << datacenterIdBits);
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }

        // 默认：只能 5 bit，也就是 0-31
        long maxWorkerId = ~(-1L << workerIdBits);
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        this.epoch = epoch;
        this.workerId = workerId;
        this.datacenterId = datacenterId;


        // 默认：机器 ID 向左移 12 位，机房 ID 向左移 17 位
        long datacenterIdShift = sequenceBits + workerIdBits;
        // 提前算好机器码
        this.machineCode = (datacenterId << datacenterIdShift) | (workerId << sequenceBits);

        // 时间戳左移位数
        this.timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
        // 自增序列掩码
        this.sequenceMask = ~(-1L << sequenceBits);
    }


    /**
     * 生成唯一 ID
     *
     * @return 雪花 ID
     */
    public synchronized long nextId() {
        // 获取当前的时间戳，单位是毫秒
        long timestamp = System.currentTimeMillis();

        // 如果当前时间小于上一次 ID 生成的时间戳，说明有人回拨了系统时间
        if (timestamp < lastTimestamp) {
            throw new ServiceException("clock moved backwards!");
        }

        // 12 位随机自增序列，也就是 0-4095
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            // 1 毫秒内，生成超出 4096 个序列
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            // 时间戳改变，毫秒内序列重置
            sequence = 0;
        }

        //上次生成ID的时间截
        lastTimestamp = timestamp;

        return ((timestamp - epoch) << timestampLeftShift) | machineCode | sequence;
    }


    /**
     * 生成唯一 ID
     *
     * @return 雪花 ID
     */
    public synchronized String nextStrId() {
        return Long.toString(this.nextId());
    }

    /**
     * 通过 while 循环，将时间推进到下 1 毫秒
     *
     * @param lastTimestamp 上次生成 ID 的时间截
     * @return 当前时间戳
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }

    // getter -------------------------------------------------------------------------------------------

    public long getDatacenterId() {
        return datacenterId;
    }

    public long getWorkerId() {
        return workerId;
    }

    public long getSequence() {
        return sequence;
    }

    public long getEpoch() {
        return epoch;
    }

    public long getDatacenterIdBits() {
        return datacenterIdBits;
    }

    public long getWorkerIdBits() {
        return workerIdBits;
    }

    public long getSequenceBits() {
        return sequenceBits;
    }

    public long getLastTimestamp() {
        return lastTimestamp;
    }

    public long getMachineCode() {
        return machineCode;
    }

    @Override
    public String toString() {
        return "Snowflake{" +
                "datacenterId=" + datacenterId +
                ", workerId=" + workerId +
                ", sequence=" + sequence +
                ", epoch=" + epoch +
                ", lastTimestamp=" + lastTimestamp +
                ", machineCode=" + machineCode +
                '}';
    }
}
